package com.zxd.interview.transactions;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.Objects;

/**
 表结构：
 <pre>
 CREATE TABLE `admin` (
 `id` int NOT NULL AUTO_INCREMENT,
 `username` varchar(50) DEFAULT NULL,
 `password` varchar(50) DEFAULT NULL,
 PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
 </pre>
 * @author zhaoxudong
 * @version v1.0.0
 * @Package : com.zxd.interview.transactions
 * @Description : 编程式事务使用方式
 * @Create on : 2021/12/13 22:06
 **/
@Component
public class ProgrammaticTrasaction {

    // 异常触发开关
    private static final boolean EXCEPTION_FLAG = true;


    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void insert(){
        // 1. 构建事务管理器，同时使用系统的数据源进行设置
        PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(Objects.requireNonNull(jdbcTemplate.getDataSource()));
        // 2. 配置事务的属性，比如事务的隔离级别，事务的超时时间以及隔离方式等等
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        // 3. 开启事务，通过默认的事务属性配置，调用platformTransactionManager.getTransaction开启事务操作，此时获取到事务的状态 TransactionStatus
        TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
        // 4. 执行和事务有关操作，这里用新增作为处理
        System.err.println("查询数据列表");

        try {
            System.err.println(jdbcTemplate.queryForList("select * from admin"));
            // 注意，jdbctemplate 把所有对于数据的改动操作统一为 update 操作，而查询则以 query 开头
            jdbcTemplate.update("insert into admin (id, username, password) values (51, '老王', '123')");
            jdbcTemplate.update("insert into admin (id, username, password) values (21, '老张', '222')");
            // 5. 提交事务
            platformTransactionManager.commit(transaction);
            //if(EXCEPTION_FLAG){
            //  throw new RuntimeException("手动抛出异常");
            //}
        } catch (Exception e) {
            // 6. 我们需要使用事务管理器的rollback方法回滚整个事务
            System.err.println("异常：触发事务回滚");
            platformTransactionManager.rollback(transaction);
        }
        System.err.println("插入数据之后");
        System.err.println(jdbcTemplate.queryForList("select * from admin"));

    }/*运行结果：
    第一次执行
    查询数据列表
    [{id=1, username=admin, password=123456}]
    插入数据之后
    [{id=1, username=admin, password=123456}, {id=21, username=老张, password=222}, {id=51, username=老王, password=123}]
    第二次执行：
    查询数据列表
    [{id=1, username=admin, password=123456}, {id=21, username=老张, password=222}, {id=51, username=老王, password=123}]
    触发事务回滚
    插入数据之后
[{id=1, username=admin, password=123456}, {id=21, username=老张, password=222}, {id=51, username=老王, password=123}]
    */

    public void update(){
        // 1. 构建事务管理器，同时使用系统的数据源进行设置
//        PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(Objects.requireNonNull(jdbcTemplate.getDataSource()));
        // 2. 配置事务的属性，比如事务的隔离级别，事务的超时时间以及隔离方式等等, 注意这里使用了实现的子类
//        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        // 2.1 设置超时时间10秒
//        transactionDefinition.setTimeout(10);
        // 2.2 设置是否只读,如果设置，则抛出异常：Connection is read-only. Queries leading to data modification are not allowed.
//        transactionDefinition.setReadOnly(true);
        // 3. 区别点，这里使用了transactionTemplate进行事务的开启和控制，下面介绍他的两种主要使用方式
        // 3.1  transactionTemplate.executeWithoutResult(Consumer<TransactionStatus> action)：
        // 没有返回值的，需传递一个Consumer对象，在accept方法中做业务操作。（类似切面）
        // 3.2 <T> T execute(TransactionCallback<T> action)：有返回值的，需要传递一个TransactionCallback对象，在doInTransaction方法中做业务操作
        // 调用execute方法或者executeWithoutResult方法执行完毕之后，事务管理器会自动提交事务或者回滚事务。
        // 事务在什么情况下会进行回滚？需要满足下面的条件：
        // （1）transactionStatus.setRollbackOnly();将事务状态标注为回滚状态
        // （2）execute方法或者executeWithoutResult方法内部抛出异常
        //  什么时候事务会提交？
        //  方法没有异常 && 未调用过transactionStatus.setRollbackOnly();
        System.err.println("事务处理前");
        System.err.println(jdbcTemplate.queryForList("select * from admin"));
        // 第一种使用方式
        transactionTemplate.executeWithoutResult(transactionStatus -> {

            jdbcTemplate.update("update admin set username ='test2'  where id = '51'");

            jdbcTemplate.update("update admin set username ='test2',password='1111'  where id = '21'");
        });
        // 第二种使用方式
        DefaultTransactionStatus execute = (DefaultTransactionStatus) transactionTemplate.execute((TransactionCallback<Object>) status -> {
            jdbcTemplate.update("update admin set username ='test2'  where id = '51'");

            jdbcTemplate.update("update admin set username ='test2',password='1111'  where id = '21'");
            return status;
        });
        System.err.println("事务处理后");
        System.err.println(jdbcTemplate.queryForList("select * from admin"));
    }/*运行结果：
        事务处理前
        [{id=1, username=admin, password=123456}, {id=21, username=老张, password=222}, {id=51, username=老王, password=123}]
        事务处理后
        [{id=1, username=admin, password=123456}, {id=21, username=test2, password=1111}, {id=51, username=test2, password=123}]
    */


}
